﻿namespace System.ServiceModel.DomainServices.Client
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Threading;

    public abstract class EntitySet : IEnumerable, INotifyCollectionChanged, IRevertibleChangeTracking, IChangeTracking, INotifyPropertyChanged
    {
        private Dictionary<AssociationAttribute, Action<Entity>> _associationUpdateCallbackMap = new Dictionary<AssociationAttribute, Action<Entity>>();
        private System.ServiceModel.DomainServices.Client.EntityContainer _entityContainer;
        private Type _entityType;
        private IDictionary<object, Entity> _identityCache;
        private List<Entity> _interestingEntities;
        private IList _list;
        private EntitySetOperations _supportedOperations;
        private PropertyChangedEventHandler propertyChanged;
        private EventHandler _collectionChangedEventHandler;

        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                PropertyChangedEventHandler handler2;
                PropertyChangedEventHandler propertyChanged = this.propertyChanged;
                do
                {
                    handler2 = propertyChanged;
                    PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler) Delegate.Combine(handler2, value);
                    propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);
                }
                while (propertyChanged != handler2);
            }
            remove
            {
                PropertyChangedEventHandler handler2;
                PropertyChangedEventHandler propertyChanged = this.propertyChanged;
                do
                {
                    handler2 = propertyChanged;
                    PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler) Delegate.Remove(handler2, value);
                    propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);
                }
                while (propertyChanged != handler2);
            }
        }

        event NotifyCollectionChangedEventHandler CollectionChanged;

        internal EntitySet(Type entityType)
        {
            if (entityType == null)
            {
                throw new ArgumentNullException("entityType");
            }
            if (!typeof(Entity).IsAssignableFrom(entityType))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.Type_Not_Entity, new object[] { entityType }), "entityType");
            }
            this._entityType = entityType;
            this._identityCache = new Dictionary<object, Entity>();
        }

        protected void AcceptChanges()
        {
            List<Entity> list = (from entity in this.InterestingEntities
                where entity.EntityState == EntityState.Deleted
                select entity).ToList<Entity>();
            List<Entity> list2 = (from entity in this.InterestingEntities
                where entity.EntityState != EntityState.Deleted
                select entity).ToList<Entity>();
            foreach (Entity entity in list)
            {
                ((IChangeTracking) entity).AcceptChanges();
            }
            foreach (Entity entity2 in list2)
            {
                ((IChangeTracking) entity2).AcceptChanges();
            }
            this._interestingEntities.Clear();
        }

        public void Add(Entity entity)
        {
            this.EnsureEntityType(entity);
            bool flag = !this.IsAttached(entity);
            this.AddInternal(entity);
            if (flag)
            {
                AddAttachInferrer.Infer(this.EntityContainer, entity, delegate (EntitySet l, Entity e) {
                    l.AddInternal(e);
                });
            }
        }

        internal void AddInternal(Entity entity)
        {
            if (entity.EntityState != EntityState.Deleted)
            {
                this.EnsureEditable(EntitySetOperations.Add);
            }
            this.EntityContainer.CheckCrossContainer(entity);
            if ((entity.EntitySet != null) && !entity.IsInferred)
            {
                throw new InvalidOperationException(Resource.EntitySet_EntityAlreadyInSet);
            }
            if (((entity.EntitySet != null) && entity.IsInferred) && (entity.EntityState != EntityState.New))
            {
                entity.IsInferred = false;
            }
            if (entity.EntityState != EntityState.Deleted)
            {
                object identity = entity.GetIdentity();
                Entity entity2 = null;
                if (((identity != null) && this._identityCache.TryGetValue(identity, out entity2)) && ((entity2.EntityState != EntityState.Deleted) && !object.ReferenceEquals(entity, entity2)))
                {
                    throw new InvalidOperationException(Resource.EntitySet_DuplicateIdentity);
                }
                entity.InitializeNew();
                this.TrackAsInteresting(entity, true);
            }
            else
            {
                entity.UndoDelete();
            }
            if (!this._list.Contains(entity))
            {
                int index = this._list.Add(entity);
                entity.EntitySet = this;
                this.OnCollectionChanged(NotifyCollectionChangedAction.Add, entity, index);
            }
        }

        internal void AddToCache(Entity entity)
        {
            object identity = entity.GetIdentity();
            if (identity == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.EntityKey_NullIdentity, new object[] { entity }));
            }
            if (this._identityCache.ContainsKey(identity))
            {
                throw new InvalidOperationException(Resource.EntitySet_DuplicateIdentity);
            }
            this._identityCache[identity] = entity;
        }

        public void Attach(Entity entity)
        {
            this.EnsureEntityType(entity);
            bool flag = !this.IsAttached(entity);
            this.AttachInternal(entity);
            if (flag)
            {
                AddAttachInferrer.Infer(this.EntityContainer, entity, delegate (EntitySet l, Entity e) {
                    l.AttachInternal(e);
                });
            }
        }

        internal void AttachInternal(Entity entity)
        {
            this.EntityContainer.CheckCrossContainer(entity);
            if (((entity.EntitySet == this) && entity.IsInferred) && (entity.EntityState == EntityState.Unmodified))
            {
                entity.IsInferred = false;
            }
            else
            {
                object identity = entity.GetIdentity();
                if (identity == null)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.EntityKey_NullIdentity, new object[] { entity }));
                }
                if (this._identityCache.ContainsKey(identity))
                {
                    throw new InvalidOperationException(Resource.EntitySet_DuplicateIdentity);
                }
                bool flag = entity.IsInferred && (entity.EntityState == EntityState.New);
                if ((entity.EntitySet != null) && !flag)
                {
                    throw new InvalidOperationException(Resource.EntitySet_EntityAlreadyAttached);
                }
                if (((entity.EntitySet != null) && entity.IsInferred) && (entity.EntityState != EntityState.Unmodified))
                {
                    entity.IsInferred = false;
                }
                entity.Reset();
                this.LoadEntity(entity);
            }
        }

        public void Clear()
        {
            if ((this._list != null) && (this.Count != 0))
            {
                bool hasChanges = this.HasChanges;
                Entity[] affectedObject = this._list.Cast<Entity>().ToArray<Entity>();
                foreach (Entity entity in affectedObject)
                {
                    entity.Reset();
                }
                this._identityCache = new Dictionary<object, Entity>();
                this._interestingEntities = null;
                this._list = this.CreateList();
                this.OnCollectionChanged(NotifyCollectionChangedAction.Reset, affectedObject, -1);
                if (hasChanges)
                {
                    this.RaisePropertyChanged("HasChanges");
                }
            }
        }

        protected abstract Entity CreateEntity();
        protected abstract IList CreateList();
        public void Detach(Entity entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            this.EnsureEntityType(entity);
            int index = -1;
            if (entity.EntitySet == this)
            {
                index = this._list.IndexOf(entity);
            }
            if ((index == -1) && ((entity.EntityState != EntityState.Deleted) || (entity.LastSet != this)))
            {
                throw new InvalidOperationException(Resource.EntitySet_EntityNotInSet);
            }
            if (index != -1)
            {
                this._list.Remove(entity);
            }
            this.RemoveFromCache(entity);
            this.TrackAsInteresting(entity, false);
            entity.Reset();
            if (entity.MetaType.HasComposition)
            {
                CompositionalChildDetacher.DetachChildren(entity);
            }
            if (index != -1)
            {
                this.OnCollectionChanged(NotifyCollectionChangedAction.Remove, entity, index);
            }
        }

        internal void EnsureEditable(EntitySetOperations operation)
        {
            if ((this._supportedOperations & operation) == EntitySetOperations.None)
            {
                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.EntitySet_UnsupportedOperation, new object[] { this._entityType, operation }));
            }
        }

        private void EnsureEntityType(object entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            if (!this._entityType.IsAssignableFrom(entity.GetType()))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.EntitySet_Wrong_Type, new object[] { this._entityType, entity.GetType() }), "entity");
            }
        }

        internal Entity GetEntityByKey(object[] keyValues)
        {
            Entity entity = null;
            object key = null;
            if (keyValues.Length == 1)
            {
                key = keyValues[0];
                if (key == null)
                {
                    throw new ArgumentNullException("keyValues", Resource.EntityKey_CannotBeNull);
                }
            }
            else
            {
                key = EntityKey.Create(keyValues);
            }
            this._identityCache.TryGetValue(key, out entity);
            return entity;
        }

        public IEnumerator GetEnumerator()
        {
            return this._list.GetEnumerator();
        }

        internal void Initialize(System.ServiceModel.DomainServices.Client.EntityContainer container, EntitySetOperations operationsToSupport)
        {
            this._entityContainer = container;
            this._supportedOperations = operationsToSupport;
            this._list = this.CreateList();
        }

        internal bool IsAttached(Entity entity)
        {
            if (entity.LastSet == null)
            {
                return false;
            }
            if (entity.EntitySet != this)
            {
                return this.InterestingEntities.Contains(entity);
            }
            return true;
        }

        internal IEnumerable<Entity> LoadEntities(IEnumerable<Entity> entities)
        {
            return this.LoadEntities(entities, LoadBehavior.KeepCurrent);
        }

        internal IEnumerable<Entity> LoadEntities(IEnumerable<Entity> entities, LoadBehavior loadBehavior)
        {
            List<Entity> list = new List<Entity>();
            foreach (Entity entity in entities)
            {
                Entity item = this.LoadEntity(entity, loadBehavior);
                list.Add(item);
            }
            return list;
        }

        internal Entity LoadEntity(Entity entity)
        {
            return this.LoadEntity(entity, LoadBehavior.KeepCurrent);
        }

        internal Entity LoadEntity(Entity entity, LoadBehavior loadBehavior)
        {
            this.EnsureEntityType(entity);
            if (entity.EntitySet != null)
            {
                throw new InvalidOperationException(Resource.EntitySet_EntityAlreadyAttached);
            }
            object identity = entity.GetIdentity();
            if (identity == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.EntityKey_NullIdentity, new object[] { entity }));
            }
            Entity entity2 = null;
            this._identityCache.TryGetValue(identity, out entity2);
            if (entity2 == null)
            {
                this.AddToCache(entity);
                entity2 = entity;
                int index = this._list.Add(entity);
                entity.MarkUnmodified();
                entity.EntitySet = this;
                if (this.CanEdit)
                {
                    entity.StartTracking();
                }
                entity.OnLoaded(true);
                this.OnCollectionChanged(NotifyCollectionChangedAction.Add, entity, index);
                return entity2;
            }
            if (!entity2.IsEditing)
            {
                if (loadBehavior != LoadBehavior.KeepCurrent)
                {
                    entity2.Merge(entity, loadBehavior);
                }
                if ((entity2.OriginalValues != null) && (loadBehavior == LoadBehavior.RefreshCurrent))
                {
                    entity2.UpdateOriginalValues(entity);
                }
            }
            return entity2;
        }

        protected virtual void OnCollectionChanged(NotifyCollectionChangedAction action, object affectedObject, int index)
        {
            if (this._collectionChangedEventHandler != null)
            {
                NotifyCollectionChangedEventArgs e = null;
                if (action == NotifyCollectionChangedAction.Add)
                {
                    e = new NotifyCollectionChangedEventArgs(action, affectedObject, index);
                }
                else if (action == NotifyCollectionChangedAction.Remove)
                {
                    e = new NotifyCollectionChangedEventArgs(action, affectedObject, index);
                }
                else if (action == NotifyCollectionChangedAction.Reset)
                {
                    e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                }
                this._collectionChangedEventHandler(this, e);
            }
            this.RaisePropertyChanged("Count");
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (e == null)
            {
                throw new ArgumentNullException("e");
            }
            if (this._entityContainer != null)
            {
                this._entityContainer.SetPropertyChanged(this, e.PropertyName);
            }
            if (this.propertyChanged != null)
            {
                this.propertyChanged(this, e);
            }
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        internal void RegisterAssociationCallback(AssociationAttribute association, Action<Entity> callback, bool register)
        {
            Action<Entity> action = null;
            this._associationUpdateCallbackMap.TryGetValue(association, out action);
            if (register)
            {
                this._associationUpdateCallbackMap[association] = (Action<Entity>) Delegate.Combine(action, callback);
            }
            else
            {
                this._associationUpdateCallbackMap[association] = (Action<Entity>) Delegate.Remove(action, callback);
            }
        }

        protected void RejectChanges()
        {
            foreach (Entity entity in this.InterestingEntities.ToList<Entity>())
            {
                if (entity.Parent == null)
                {
                    if (entity.EntityState == EntityState.New)
                    {
                        this.Remove(entity);
                    }
                    else if (entity.EntityState == EntityState.Deleted)
                    {
                        this.Add(entity);
                    }
                }
                ((IRevertibleChangeTracking) entity).RejectChanges();
            }
            this._interestingEntities.Clear();
        }

        public void Remove(Entity entity)
        {
            this.EnsureEntityType(entity);
            if (entity.MetaType.HasComposition)
            {
                CompositionalChildRemover.RemoveChildren(entity);
            }
            if (entity.EntityState != EntityState.New)
            {
                this.EnsureEditable(EntitySetOperations.Remove);
            }
            int index = -1;
            if (entity.EntitySet == this)
            {
                index = this._list.IndexOf(entity);
            }
            if (index == -1)
            {
                throw new InvalidOperationException(Resource.EntitySet_EntityNotInSet);
            }
            if ((entity.EntitySet != null) && entity.IsInferred)
            {
                entity.IsInferred = false;
            }
            if (entity.EntityState != EntityState.New)
            {
                entity.MarkDeleted();
                this.TrackAsInteresting(entity, true);
            }
            else
            {
                entity.MarkDetached();
                this.TrackAsInteresting(entity, false);
            }
            entity.EntitySet = null;
            this._list.Remove(entity);
            this.OnCollectionChanged(NotifyCollectionChangedAction.Remove, entity, index);
        }

        internal void RemoveFromCache(Entity entity)
        {
            Entity entity2;
            object identity = entity.GetIdentity();
            if (((identity == null) || !this._identityCache.TryGetValue(identity, out entity2)) || (entity2 != entity))
            {
                foreach (KeyValuePair<object, Entity> pair in this._identityCache)
                {
                    if (object.ReferenceEquals(pair.Value, entity))
                    {
                        this._identityCache.Remove(pair.Key);
                        break;
                    }
                }
            }
            else
            {
                this._identityCache.Remove(identity);
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        void IChangeTracking.AcceptChanges()
        {
            this.AcceptChanges();
        }

        void IRevertibleChangeTracking.RejectChanges()
        {
            this.RejectChanges();
        }

        internal void TrackAsInteresting(Entity entity, bool isInteresting)
        {
            if (isInteresting)
            {
                if (!this.InterestingEntities.Contains(entity))
                {
                    this._interestingEntities.Add(entity);
                    if (this._interestingEntities.Count == 1)
                    {
                        this.RaisePropertyChanged("HasChanges");
                    }
                }
            }
            else if (this._interestingEntities != null)
            {
                int count = this._interestingEntities.Count;
                this._interestingEntities.Remove(entity);
                if ((this._interestingEntities.Count == 0) && (count == 1))
                {
                    this.RaisePropertyChanged("HasChanges");
                }
            }
        }

        internal void UpdateRelatedAssociations(Entity entity, string propertyName)
        {
            bool entityChangesAccepted = ((this.CanEdit || this.CanAdd) && (string.Compare(propertyName, "EntityState", StringComparison.Ordinal) == 0)) && (entity.EntityState == EntityState.Unmodified);
            foreach (Action<Entity> action in (from p in Enumerable.Where<KeyValuePair<AssociationAttribute, Action<Entity>>>(this._associationUpdateCallbackMap, delegate (KeyValuePair<AssociationAttribute, Action<Entity>> p) {
                if (p.Value == null)
                {
                    return false;
                }
                if (!entityChangesAccepted)
                {
                    return p.Key.OtherKeyMembers.Contains<string>(propertyName);
                }
                return true;
            }) select p.Value).ToArray<Action<Entity>>())
            {
                action(entity);
            }
        }

        public bool CanAdd
        {
            get
            {
                return ((this._supportedOperations & EntitySetOperations.Add) != EntitySetOperations.None);
            }
        }

        public bool CanEdit
        {
            get
            {
                return ((this._supportedOperations & EntitySetOperations.Edit) != EntitySetOperations.None);
            }
        }

        public bool CanRemove
        {
            get
            {
                return ((this._supportedOperations & EntitySetOperations.Remove) != EntitySetOperations.None);
            }
        }

        public int Count
        {
            get
            {
                return this._list.Count;
            }
        }

        public System.ServiceModel.DomainServices.Client.EntityContainer EntityContainer
        {
            get
            {
                return this._entityContainer;
            }
        }

        public Type EntityType
        {
            get
            {
                return this._entityType;
            }
        }

        public bool HasChanges
        {
            get
            {
                foreach (Entity entity in this.InterestingEntities)
                {
                    if (((entity.EntityState == EntityState.New) || (entity.EntityState == EntityState.Deleted)) || ((IChangeTracking) entity).IsChanged)
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        internal ReadOnlyCollection<Entity> InterestingEntities
        {
            get
            {
                if (this._interestingEntities == null)
                {
                    this._interestingEntities = new List<Entity>();
                }
                return this._interestingEntities.AsReadOnly();
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return (this._supportedOperations == EntitySetOperations.None);
            }
        }

        protected internal IList List
        {
            get
            {
                return this._list;
            }
        }

        bool IChangeTracking.IsChanged
        {
            get
            {
                return this.HasChanges;
            }
        }

        private class AddAttachInferrer : EntityVisitor
        {
            private Action<EntitySet, Entity> _action;
            private EntityContainer _container;
            private bool _isTopLevel = true;
            private Dictionary<Entity, bool> _visited = new Dictionary<Entity, bool>();

            private AddAttachInferrer(EntityContainer container, Action<EntitySet, Entity> action)
            {
                this._container = container;
                this._action = action;
            }

            public static void Infer(EntityContainer container, Entity entity, Action<EntitySet, Entity> action)
            {
                if (container == null)
                {
                    throw new ArgumentNullException("container");
                }
                if (entity == null)
                {
                    throw new ArgumentNullException("entity");
                }
                if (action == null)
                {
                    throw new ArgumentNullException("action");
                }
                new EntitySet.AddAttachInferrer(container, action).Visit(entity);
            }

            public override void Visit(Entity entity)
            {
                if (!this._visited.ContainsKey(entity))
                {
                    EntitySet entitySet = this._container.GetEntitySet(entity.GetType());
                    if (!this._isTopLevel && !entitySet.IsAttached(entity))
                    {
                        entity.IsInferred = true;
                        this._action(entitySet, entity);
                    }
                    this._visited.Add(entity, true);
                    this._isTopLevel = false;
                    base.Visit(entity);
                }
            }

            protected override void VisitEntityCollection(IEntityCollection entityCollection, PropertyInfo propertyInfo)
            {
                if ((entityCollection != null) && entityCollection.HasValues)
                {
                    foreach (Entity entity in entityCollection.Entities)
                    {
                        this.Visit(entity);
                    }
                }
            }

            protected override void VisitEntityRef(IEntityRef entityRef, Entity parent, PropertyInfo propertyInfo)
            {
                if ((entityRef != null) && entityRef.HasValue)
                {
                    this.Visit(entityRef.Entity);
                }
            }
        }

        internal class CompositionalChildDetacher : EntityVisitor
        {
            private CompositionalChildDetacher()
            {
            }

            public static void DetachChildren(Entity entity)
            {
                new EntitySet.CompositionalChildDetacher().Visit(entity);
            }

            protected override void VisitEntityCollection(IEntityCollection entityCollection, PropertyInfo propertyInfo)
            {
                if (propertyInfo.GetCustomAttributes(typeof(CompositionAttribute), false).Any<object>())
                {
                    foreach (Entity entity in entityCollection.Entities.ToArray<Entity>())
                    {
                        if (entity.EntitySet != null)
                        {
                            entity.EntitySet.Detach(entity);
                        }
                    }
                }
            }

            protected override void VisitEntityRef(IEntityRef entityRef, Entity parent, PropertyInfo propertyInfo)
            {
                if (propertyInfo.GetCustomAttributes(typeof(CompositionAttribute), false).Any<object>())
                {
                    Entity entity = null;
                    if (entityRef == null)
                    {
                        entity = (Entity) propertyInfo.GetValue(parent, null);
                    }
                    else
                    {
                        entity = entityRef.Entity;
                    }
                    if ((entity != null) && (entity.EntitySet != null))
                    {
                        entity.EntitySet.Detach(entity);
                    }
                }
            }
        }

        internal class CompositionalChildRemover : EntityVisitor
        {
            private CompositionalChildRemover()
            {
            }

            public static void RemoveChildren(Entity entity)
            {
                new EntitySet.CompositionalChildRemover().Visit(entity);
            }

            protected override void VisitEntityCollection(IEntityCollection entityCollection, PropertyInfo propertyInfo)
            {
                if (propertyInfo.GetCustomAttributes(typeof(CompositionAttribute), false).Any<object>())
                {
                    foreach (Entity entity in entityCollection.Entities.ToArray<Entity>())
                    {
                        entityCollection.Remove(entity);
                        this.Visit(entity);
                    }
                }
            }

            protected override void VisitEntityRef(IEntityRef entityRef, Entity parent, PropertyInfo propertyInfo)
            {
                if (propertyInfo.GetCustomAttributes(typeof(CompositionAttribute), false).Any<object>())
                {
                    Entity entity = null;
                    if (entityRef == null)
                    {
                        entity = (Entity) propertyInfo.GetValue(parent, null);
                    }
                    else
                    {
                        entity = entityRef.Entity;
                    }
                    propertyInfo.SetValue(parent, null, null);
                    if (entity != null)
                    {
                        this.Visit(entity);
                    }
                }
            }
        }
    }
}

